package dbms;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class FileMgr {

	public static String osName = System.getProperty("os.name");

	private DTD dtd = new DTD();
	private LinkedList<String> columnName = new LinkedList<String>();
	private LinkedList<String> columnType = new LinkedList<String>();
	private LinkedList<Boolean> columnIsNullable = new LinkedList<Boolean>();

	//

	private String getDTDColumnType(String s) {
		int i = s.length() - 3;
		String ans = "";
		while (s.charAt(i) != '"') {
			ans = s.charAt(i) + ans;
			i--;
		}
		return ans;
	}

	private Column<?> getColumn(String name, final String type) {
		Column out = new Column(name) {
			public int type() {
				return Type.getTypeID(type);
			}
		};
		if (type.contains("VARCHAR")) {
			String type2 = type;
			type2 = type2.replaceAll("VARCHAR\\(", "");
			type2 = type2.replaceAll("\\)", "");
			out.setStringMaxLength(Integer.parseInt(type2));
		}
		return out;
	}

	public static String getRootDir() {
		return System.getProperty("user.home") + getFileSep() + "Documents" + getFileSep() + "DB" + getFileSep();
	}

	public static char getFileSep() {
		if (osName.contains("Linux") || osName.contains("Unix"))
			return '/';
		else if (osName.contains("Windows"))
			return '\\';
		return '/';
	}

	public static ArrayList<String> getDataBaseNames() {
		File[] list = new File(getRootDir()).listFiles();
		ArrayList<String> out = new ArrayList<String>();
		for (File file : list)
			if (file.isDirectory())
				out.add(file.getName());
		return out;
	}

	public static ArrayList<String> getTableNames(String dbName) {
		File[] list = new File(getRootDir() + dbName).listFiles();
		ArrayList<String> out = new ArrayList<String>();
		for (File file : list) {
			String tbl = file.getName().replace(".xml", "");
			if (tbl.indexOf('.') == -1)
				out.add(tbl);
		}
		return out;
	}

	//

	public Table loadTable(String tbl, String database) throws DBException {
		Table out = null;
		try {
			out = readTable(tbl, getRootDir() + database + getFileSep() + tbl);
		} catch (IOException e) {
			throw new DBException(DBException.TBL_NOTFOUND);
		} catch (SAXException e) {
			e.printStackTrace();
			throw new DBException(DBException.TBL_CORRUPT);
		} catch (Exception e) {
			e.printStackTrace();
			throw new DBException(DBException.UNKNOWN);
		}
		return out;
	}

	public void saveTable(Table tbl, String database) throws ParserConfigurationException, DBException, IOException {
		int i = -1;
		String saveDir = getRootDir() + database + getFileSep();
		while (i < saveDir.length()) {
			i = saveDir.indexOf(getFileSep(), i + 1);
			if (i == -1)
				break;
			File dir = new File(saveDir.substring(0, i) + getFileSep());
			if (!dir.exists())
				dir.mkdirs();
		}
		writeTable(tbl, saveDir + tbl.getName());
	}

	private Table readTable(String tbl, String url) throws SAXException, IOException, ParserConfigurationException, DOMException, DBException {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.newDocument();
		doc = db.newDocument();
		doc = db.parse(new File(url + ".xml"));
		Table table = new Table(tbl);
		table.setName(tbl);

		File f = new File(url + ".dtd");

		BufferedReader r = new BufferedReader(new FileReader(f));
		StringBuilder sb = new StringBuilder();
		String st = "";
		while ((st = r.readLine()) != null)
			sb.append(st + "\n");

		sb = new StringBuilder(dtd.getTable(sb.toString(), tbl));
		BufferedReader read = new BufferedReader(new StringReader(sb.toString()));

		Element rootElement = (Element) doc.getFirstChild();
		NodeList columnList = rootElement.getChildNodes();
		read.readLine();
		read.readLine();

		for (int i = 0; i < columnList.getLength(); i++)
			if (!columnList.item(i).getNodeName().equals("#text")) {
				read.readLine();
				Element columnXml = (Element) columnList.item(i);
				final Column<?> column = getColumn(columnXml.getNodeName(), getDTDColumnType(read.readLine()));
				column.setNullable(Boolean.parseBoolean(read.readLine()));
				NodeList cellList = columnXml.getChildNodes();
				for (int j = 0; j < cellList.getLength(); j++)
					if (!cellList.item(j).getNodeName().equals("#text")) {
						column.addCell(new Cell(Type.parse((cellList.item(j)).getTextContent(), column.type())) {
							public int type() {
								return Type.getTypeID(column.getType());
							}

						});
					}
				table.addColumn(column);
			}

		return table;
	}

	private void writeTable(Table table, String url) throws ParserConfigurationException, DBException, IOException {
		Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();

		Element rootElement = doc.createElement(table.getName());

		doc.appendChild(rootElement);
		Iterator<Entry<String, Column<?>>> i = table.getColumnMap().iterator();
		while (i.hasNext()) {
			String s = i.next().getKey();
			columnName.add(s);
			Column<?> c = table.getColumn(s);
			columnType.add(c.getType());

			columnIsNullable.add(c.isNullable());

			ArrayList<?> cells = c.getCells();
			Element col = doc.createElement(s);
			for (int j = 0; j < cells.size(); j++) {
				Cell<?> cell = (Cell<?>) cells.get(j);
				Element cel = doc.createElement("Cell"/* + j*/);
				cel.setTextContent(cell.getValue() + "");
				col.appendChild(cel);
			}
			rootElement.appendChild(col);
		}
		//
		StringBuilder sb = new StringBuilder();
		File f = new File(url + ".dtd");
		if (f.exists()) {
			BufferedReader reader = new BufferedReader(new FileReader(f));

			String st = "";
			while ((st = reader.readLine()) != null)
				sb.append(st + "\n");
			sb = new StringBuilder(dtd.deleteTable(sb.toString(), table.getName()));
		}
		//
		BufferedWriter writer = new BufferedWriter(new FileWriter(f));
		writer.write(sb.append(creatDTDTable(table.getName())).toString());
		writer.close();
		//
		try {
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(new File(url + ".xml"));
			transformer.transform(source, result);
		} catch (TransformerException e) {}
	}

	//

	private String creatDTDTable(String tableName) {
		StringBuilder st = new StringBuilder();
		st.append(dtd.tableLine(tableName) + "\n");
		Iterator<String> i = columnName.iterator();
		Iterator<String> i1 = columnName.iterator();
		Iterator<String> i2 = columnType.iterator();
		Iterator<Boolean> i3 = columnIsNullable.iterator();

		String x = "(";
		while (i.hasNext()) {
			x += i.next() + ",";

		}
		x = x.substring(0, x.length() - 1) + ")";
		st.append(dtd.setColums(x, tableName) + "\n");
		while (i1.hasNext()) {
			st.append(dtd.columnEleAtt(i2.next(), i3.next() + "", i1.next()) + "\n");
		}
		st.append("]>\n");
		return st.toString();
	}

	// MAIN

	public static void main(String[] args) throws DBException {
		System.out.println(System.getProperty("os.name"));
		System.out.println(getRootDir());
		//
		Table t = new Table("Table");
		t.addColumn(new Column<Integer>("ID") {
			@Override
			public int type() {
				return Type.INTEGER;
			}
		});
		t.addColumn(new Column<String>("Name", 15) {
			@Override
			public int type() {
				return Type.STRING;
			}
		});
		t.addColumn(new Column<Integer>("Grade") {
			@Override
			public int type() {
				return Type.INTEGER;
			}
		});
		System.out.println(Arrays.deepToString(t.getColumnNames()));
		for (int i = 0; i < 5; i++) {
			HashMap<String, Object> m = new HashMap<String, Object>();
			m.put("ID", i);
			m.put("Name", "Buddy" + i);
			m.put("Grade", (i * 25) % 60 + 40);
			t.addRow(m);
		}
		System.out.println(t);
		// System.out.println(Arrays.deepToString(t.getTable()));
		FileMgr f = new FileMgr();
		try {
			f.saveTable(t, "db");
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			System.out.println(f.loadTable("Table", "db"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
